import { ref, computed } from "vue";
import type { EventType, ServerSentEvent } from "@/types/sse.types";
import { createEventStream, type ServerSentEventCallbacks } from "@/api/stream";

// Type for the event callback function
export type EventCallback = (event: ServerSentEvent) => void;

class EventEngine {
  // generate uuid and use it as sessionId
  public sessionId = crypto.randomUUID();
  // Constants
  private readonly RECONNECT_DELAY = 1000;

  // State
  private isConnected = ref(false);
  private isConnecting = ref(false);
  private lastConnectionTime = ref<Date | null>(null);
  private sseConnection = ref<ReturnType<typeof createEventStream> | null>(
    null
  );
  private reconnectTimeout = ref<NodeJS.Timeout | null>(null);

  // Event callbacks registry
  private eventCallbacks = new Map<string, Set<EventCallback>>();

  /**
   * Get connection status
   */
  public getConnectionStatus() {
    return {
      // Use computed to make ref read-only and maintain reactivity
      isConnected: computed(() => this.isConnected.value),
      isConnecting: computed(() => this.isConnecting.value),
      lastConnectionTime: computed(() => this.lastConnectionTime.value)
    };
  }

  /**
   * Connect to the SSE endpoint
   * @returns A promise that resolves when the connection is established
   */
  public async connect(): Promise<void> {
    console.log("EventEngine is now connecting to SSE endpoint");
    if (this.isConnecting.value) {
      console.log("EventEngine is connecting, skipping");
      return;
    }
    this.isConnecting.value = true;

    // Close existing connection if any
    this.disconnect();

    // Define callbacks for the SSE connection
    const callbacks: ServerSentEventCallbacks = {
      onOpen: response => {
        console.log("EventEngine event stream connected:", response.status);
        this.isConnected.value = true;
        this.isConnecting.value = false;
        this.lastConnectionTime.value = new Date();

        // Clear any reconnect timeout
        if (this.reconnectTimeout.value) {
          clearTimeout(this.reconnectTimeout.value);
          this.reconnectTimeout.value = null;
        }
      },
      onEvent: this.dispatchEvent.bind(this),
      onError: err => {
        console.error("EventEngine event stream error:", err);
        this.isConnecting.value = false;
        if (this.isConnected.value) {
          this.disconnect();
        }
        this.scheduleReconnect();
      },
      onClose: () => {
        console.warn("EventEngine event stream closed");
        this.isConnecting.value = false;
        this.disconnect();
        this.scheduleReconnect();
      }
    };

    // Configure the SSE heartbeat
    const config = {
      params: {
        session_id: this.sessionId
      },
      heartbeat: {
        timeoutMs: 6000, // 默认6秒超时
        checkIntervalMs: 3000 // 默认3秒检查一次
      }
    };

    // Create the SSE connection
    this.sseConnection.value = createEventStream(callbacks, config);

    // Ensure connection is established with a timeout
    return new Promise<void>((resolve, reject) => {
      const connectionTimeout = setTimeout(() => {
        if (!this.isConnected.value) {
          clearInterval(checkConnection);
          this.disconnect(); // Ensure we clean up properly on timeout
          reject(new Error("Timeout while connecting to event stream"));
        }
      }, 5000);

      const checkConnection = setInterval(() => {
        if (this.isConnected.value) {
          clearTimeout(connectionTimeout);
          clearInterval(checkConnection);
          resolve();
        }
      }, 100);
    });
  }

  /**
   * Disconnect from the SSE endpoint
   */
  public disconnect(): void {
    if (this.sseConnection.value) {
      try {
        this.sseConnection.value.close();
      } catch (err) {
        console.error("Error closing EventEngine connection:", err);
      } finally {
        this.sseConnection.value = null;
        this.isConnected.value = false;
      }
    }
  }

  /** Dispose */
  public dispose(): void {
    this.disconnect();
    clearTimeout(this.reconnectTimeout.value);
    this.reconnectTimeout.value = null;
  }

  /**
   * Register a callback for a specific event type
   * @param eventType The event type to listen for
   * @param callback The callback function to call when the event occurs
   */
  public on(eventType: EventType, callback: EventCallback): void {
    // Get or create the set of callbacks for this event type
    if (!this.eventCallbacks.has(eventType)) {
      this.eventCallbacks.set(eventType, new Set());
    }

    // Add the callback to the set
    this.eventCallbacks.get(eventType)!.add(callback);

    // Connect if not already connected
    if (!this.isConnected.value && !this.isConnecting.value) {
      this.connect().catch(err => {
        console.error("Failed to connect when registering event handler:", err);
      });
    }
  }

  /**
   * Unregister a callback for a specific event type
   * @param eventType The event type to stop listening for
   * @param callback The callback function to remove
   */
  public off(eventType: EventType, callback: EventCallback): void {
    const callbacks = this.eventCallbacks.get(eventType);
    if (callbacks) {
      callbacks.delete(callback);

      // If no more callbacks for this event type, remove the entry
      if (callbacks.size === 0) {
        this.eventCallbacks.delete(eventType);
      }
    }
  }

  /**
   * Dispatch an event to registered callbacks
   * @param event The event to dispatch
   */
  private dispatchEvent(event: ServerSentEvent): void {
    const callbacks = this.eventCallbacks.get(event.event_type);

    if (!callbacks || callbacks.size === 0) {
      return;
    }

    // Call all registered callbacks
    for (const callback of callbacks) {
      try {
        callback(event);
      } catch (err) {
        console.error(`Error in callback for event ${event.event_type}:`, err);
      }
    }
  }

  /**
   * Schedule a reconnection attempt
   */
  private scheduleReconnect(): void {
    // Cancel any existing reconnect timeout
    if (this.reconnectTimeout.value) {
      clearTimeout(this.reconnectTimeout.value);
      this.reconnectTimeout.value = null;
    }

    console.log(
      `EventEngine scheduling reconnection in ${this.RECONNECT_DELAY}ms`
    );

    this.reconnectTimeout.value = setTimeout(async () => {
      this.reconnectTimeout.value = null;
      try {
        console.log("EventEngine attempting to reconnect");
        await this.connect();
      } catch (err) {
        console.error("EventEngine reconnection attempt failed:", err);
      }
    }, this.RECONNECT_DELAY);
  }
}

// Create singleton instance
export const eventEngine = new EventEngine();
